package com.ziq.base.manager

import android.os.Handler
import android.os.Looper
import com.ziq.base.service.ConnectInfo
import com.ziq.base.service.MessageProto
import com.ziq.base.service.MessageProto.Companion.OpAuth
import com.ziq.base.utils.LogUtil
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.WebSocket
import okhttp3.WebSocketListener
import okio.ByteString
import okio.ByteString.Companion.toByteString
import java.security.KeyManagementException
import java.security.NoSuchAlgorithmException
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Singleton
import javax.net.ssl.HostnameVerifier
import javax.net.ssl.SSLSession


@Singleton
class WebSocketManager @Inject constructor() {

    companion object {
        const val TAG = "WebSocketManager"
    }

    var state = 0 // 0 未连接 1 连接中 2 已连接
    var action = 0 //0 默认 1 连接 2 主动断开
    private var mCurrentWebSocket : WebSocket? = null
    private var mCurrentConnectInfo : ConnectInfo? = null
    private val handler = Handler(Looper.getMainLooper())

    var onMessageReceive : ((byteArray:ByteArray)->Unit)? = null

    private fun invalidConnectInfo(info: ConnectInfo) : Boolean{
        if(info.url.isNullOrEmpty()){
            return true
        }
        if(info.uid == 0){
            return true
        }
        return false
    }

    private fun isSameConnectInfo(info: ConnectInfo): Boolean{
        if(mCurrentConnectInfo == null){
            return false
        }
        if(mCurrentConnectInfo?.url == info.url && mCurrentConnectInfo?.uid == info.uid){
            return true
        }
        return false
    }

    fun connect(info: ConnectInfo){
        if(invalidConnectInfo(info)){
            return
        }
        if(isSameConnectInfo(info)){
            if(state != 0){
                return
            }
        }
        mCurrentConnectInfo = info
        handler.removeCallbacks(reconnectRunnable)
        action = 1
        connect()
    }

    private fun connect(){
        mCurrentWebSocket?.cancel()
        val client = generateClient(5000L,10000L,10000L,10000L)
        val request = generateRequest(hashMapOf(), mCurrentConnectInfo?.url?:"")
        if(client != null && request != null){
            state = 1
            client.newWebSocket(request, object : WebSocketListener() {

                override fun onOpen(webSocket: WebSocket, response: Response) {
                    super.onOpen(webSocket, response)
                    onConnected(webSocket)
                    LogUtil.i(TAG, "onOpen : ${mCurrentConnectInfo?.url?:""}")
                }

                override fun onMessage(webSocket: WebSocket, text: String) {
                    super.onMessage(webSocket, text)
                    onMessageReceive?.invoke(text.toByteArray())
                }

                override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
                    super.onMessage(webSocket, bytes)
                    onMessageReceive?.invoke(bytes.toByteArray())
                }

                override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
                    super.onClosing(webSocket, code, reason)
                    LogUtil.i(TAG, "onClosing : $code $reason")
                    state = 0
                }

                override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
                    super.onClosed(webSocket, code, reason)
                    LogUtil.i(TAG, "onClosed : $code $reason")
                    onDisconnected()
                }

                override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
                    super.onFailure(webSocket, t, response)
                    LogUtil.i(TAG, "onFailure : $t")
                    onDisconnected()
                }
            })
        }
    }

    fun disconnect(){
        action = 2
        mCurrentWebSocket?.cancel()
    }

    private fun onConnected(webSocket: WebSocket){
        state = 2
        action = 0
        mCurrentWebSocket = webSocket
        auth()
    }

    private fun onDisconnected(){
        state = 0
        mCurrentWebSocket = null
        if(action != 2){
            reconnect()
        }
        action = 0
    }


    private val reconnectTimeLimit = 4
    private var reconnectTime = 0
    private fun reconnect(){
        handler.removeCallbacks(reconnectRunnable)
        if(reconnectTime < reconnectTimeLimit){
            handler.postDelayed(reconnectRunnable, 5000)
        }
    }

    private val reconnectRunnable = Runnable {
        reconnectTime++
        connect()
    }

    fun send(bytes : ByteArray){
        mCurrentWebSocket?.send(bytes.toByteString())
    }


    private fun auth(){
        val bodyByteArray = "{\"mid\":${mCurrentConnectInfo?.uid?:0}, \"room_id\":\"live://${mCurrentConnectInfo?.roomId?:0}\", \"platform\":\"${mCurrentConnectInfo?.platform?:""}\", \"accepts\":${mCurrentConnectInfo?.accepts?.contentToString()?:"[1000]"}}".toByteArray()
        val message = MessageProto()
        message.version = 1
        message.op = OpAuth
        message.seq = 1
        message.body = bodyByteArray
        send(message.getProtoByteArray())
    }


    private fun generateClient(
        interval: Long,
        connectTimeout: Long,
        readTimeout: Long,
        writeTimeout: Long
    ): OkHttpClient? {
        return try {
            OkHttpClient.Builder()
                .hostnameVerifier(object : HostnameVerifier {
                    override fun verify(hostname: String?, session: SSLSession?): Boolean {
                        return true
                    }
                })
                .pingInterval(interval, TimeUnit.MILLISECONDS)
                .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
                .readTimeout(readTimeout, TimeUnit.MILLISECONDS)
                .writeTimeout(writeTimeout, TimeUnit.MILLISECONDS)
                .build()
        } catch (e: NoSuchAlgorithmException) {
            e.printStackTrace()
            null
        } catch (e: KeyManagementException) {
            e.printStackTrace()
            null
        }
    }


    private fun generateRequest(headers: HashMap<String, String>?, url: String): Request? {
        val requestBuilder: Request.Builder = Request.Builder()
        if (headers != null) {
            for (entry in headers) {
                val key: String = entry.key
                val value: String = entry.value
                requestBuilder.addHeader(key, value)
            }
        }
        requestBuilder.url(url)
        return requestBuilder.build()
    }


}